<?php

require_once 'database_engine/common_engine.php';
require_once 'components/utils/output_utils.php';
require_once 'components/utils/string_utils.php';

class MySqlConnection implements IConnection
{
    /** @var array */
    private $connectionOptions;

    /** @var \resource|null */
    private $connectionHandle;

    /** @var boolean */
    private $opened;

    /**
     * @param array $connectionOptions
     */
    public function __construct($connectionOptions)
    {
        $this->connectionOptions = $connectionOptions;
        $this->connectionHandle = null;
        $this->opened = false;
    }

    /**
     * @throws SqlException,ConnectionFailedException
     * @return void
     */
    public final function Open()
    {
        $serverString = $this->connectionOptions[ConnectionOption::Host];

        if (isset($this->connectionOptions[ConnectionOption::Port]))
            $serverString .= ':' . $this->connectionOptions[ConnectionOption::Port];

        $output = null;
        set_error_handler(function($errno, $errstr) use (&$output)
        {
            $output = $errstr;
            return false;
        });

        $this->connectionHandle = @mysql_connect(
            $serverString,
            $this->connectionOptions[ConnectionOption::Login],
            $this->connectionOptions[ConnectionOption::Password]
        );
        restore_error_handler();

        if (!$this->connectionHandle)
        {
            throw new ConnectionFailedException($output);
        }
        else
        {
            $selectDbResult = mysql_select_db(
                $this->connectionOptions[ConnectionOption::DatabaseName],
                $this->connectionHandle
            );
            if ($selectDbResult)
            {
                $this->opened = true;
            }
            else
            {
                mysql_close($this->connectionHandle);
                throw new ConnectionFailedException('mysql_select_db: Unable to connect to database' . $this->connectionOptions[ConnectionOption::DatabaseName]);
            }
        }
    }

    /**
     * @return void
     */
    public final function Close()
    {
        mysql_close($this->connectionHandle);
    }

    /**
     * @throws SqlException
     * @param string $sql
     * @return IDataReader
     */
    public final function ExecuteReader($sql)
    {
        $this->CheckOpened();
        $result = new MySqlDataReader($this, $sql);
        $result->Open();
        return $result;
    }

    /**
     * @throws SqlException
     * @param string $sql
     * @return void
     */
    public final function ExecuteNoResult($sql)
    {
        $this->CheckOpened();
        @mysql_query($sql, $this->GetConnectionHandle());
    }

    /**
     * @throws SqlException
     * @param string $sql
     * @return mixed
     */
    public final function ExecuteScalar($sql)
    {
        // TODO: Implement ExecuteScalar() method.
    }

    /**
     * @return boolean
     */
    public final function Opened()
    {
        return $this->opened;
    }

    /**
     * @return null|resource
     */
    public final function GetConnectionHandle()
    {
        return $this->connectionHandle;
    }

    /**
     * @throws SqlException
     * @return void
     */
    public final function CheckLastError()
    {
        $errorText = mysql_error($this->GetConnectionHandle());
        if (!StringUtils::IsNullOrEmpty($errorText))
            throw new SqlException($errorText);
    }

    /**
     * @throws SqlException
     * @return void
     */
    private function CheckOpened()
    {
        if(!$this->Opened())
            throw new SqlException('Connection is not opened');
    }
}

class MySqlDataReader implements IDataReader
{
    /** @var MySqlConnection */
    private $connection;

    /** @var string */
    private $sql;

    /** @var null|resource */
    private $queryHandle;

    /** @var array */
    private $lastFetchedRow;

    /** @var array */
    private $fieldInfos;
    const FieldName = 'field_name';
    const IsFieldPrimaryKey = 'is_primary';
    
    /**
     * @param MySqlConnection $connection
     * @param string $sql
     */
    public function __construct(MySqlConnection $connection, $sql)
    {
        $this->connection = $connection;
        $this->sql = $sql;
        $this->queryHandle = null;
        $this->lastFetchedRow = null;
        $this->fieldInfos = array();
    }

    /**
     * @return void
     */
    private function RetrieveFieldInfo()
    {
        $fieldCount = @mysql_num_fields($this->queryHandle);
        $this->CheckLastError();
        for($i = 0; $i < $fieldCount; $i++)
        {
            $fieldInfo = mysql_fetch_field($this->queryHandle, $i);
            $this->fieldInfos[] = array(
                self::FieldName => $fieldInfo->name,
                self::IsFieldPrimaryKey => ($fieldInfo->primary_key == 1 ? true : false)
            );
        }
    }

    /**
     * @throws SqlException
     * @return void
     */
    private function CheckOpened()
    {
        if (!$this->queryHandle)
            throw new SqlException('DataReader is not opened');
    }

    /**
     * @return void
     */
    private function CheckLastError()
    {
        $this->connection->CheckLastError();
    }

    private function GetConnectionHandle()
    {
        return $this->connection->GetConnectionHandle();
    }

    /**
     * @throws SqlException
     * @return void
     */
    public final function Open()
    {
        $this->queryHandle = @mysql_query($this->sql, $this->GetConnectionHandle());
        $this->CheckLastError();
        $this->RetrieveFieldInfo();
    }

    /**
     * @throws SqlException
     * @return boolean
     */
    public final function Next()
    {
        $fetchedRow = @mysql_fetch_array($this->queryHandle, MYSQL_ASSOC);
        $this->CheckLastError();

        if ($this->lastFetchedRow)
        {
            $this->lastFetchedRow = $fetchedRow;
            return true;
        }
        else
            return false;
    }

    /**
     * @throws SqlException
     * @return void
     */
    public final function Close()
    {
        @mysql_close($this->queryHandle);
        $this->CheckLastError();
        $this->queryHandle = null;
    }

    /**
     * @throws SqlException
     * @param string $fieldName
     * @return mixed
     */
    public final function GetFieldValueByName($fieldName)
    {
        if ($this->lastFetchedRow == null)
            throw new SqlException('GetFieldValueByName: DataReader is not opened or method Next is not invoked');
        return $this->lastFetchedRow[$fieldName];
    }

    /**
     * @throws SqlException
     * @return string[]
     */
    public final function GetFieldNames()
    {
        $this->CheckOpened();
        $result = array();
        array_walk($this->fieldInfos, function($value) use (&$result) { $result[] = $value[MySqlDataReader::FieldName]; });
        return $result;
    }
    /**
     * @throws SqlException
     * @param string $fieldName
     * @return boolean
     */
    public final function IsPrimaryKeyField($fieldName)
    {
        $this->CheckOpened();
        foreach($this->fieldInfos as $fieldInfo)
            if ($fieldInfo[MySqlDataReader::FieldName] == $fieldName &&
                $fieldInfo[MySqlDataReader::IsFieldPrimaryKey])
                return true;
        return false;
    }
}

?>